View Javadoc
1 package org.apache.commons.betwixt.digester; 2 3 /* 4 * ==================================================================== 5 * 6 * The Apache Software License, Version 1.1 7 * 8 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights 9 * reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in 20 * the documentation and/or other materials provided with the 21 * distribution. 22 * 23 * 3. The end-user documentation included with the redistribution, if 24 * any, must include the following acknowlegement: 25 * "This product includes software developed by the 26 * Apache Software Foundation (http://www.apache.org/)." 27 * Alternately, this acknowlegement may appear in the software itself, 28 * if and wherever such third-party acknowlegements normally appear. 29 * 30 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 31 * Foundation" must not be used to endorse or promote products derived 32 * from this software without prior written permission. For written 33 * permission, please contact apache@apache.org. 34 * 35 * 5. Products derived from this software may not be called "Apache" 36 * nor may "Apache" appear in their names without prior written 37 * permission of the Apache Group. 38 * 39 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 40 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 42 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 43 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 45 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 46 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 47 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 48 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 49 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 50 * SUCH DAMAGE. 51 * ==================================================================== 52 * 53 * This software consists of voluntary contributions made by many 54 * individuals on behalf of the Apache Software Foundation. For more 55 * information on the Apache Software Foundation, please see 56 * <http://www.apache.org/>;. 57 */ 58 59 import java.beans.Introspector; 60 import java.beans.IntrospectionException; 61 import java.beans.PropertyDescriptor; 62 import java.lang.reflect.Method; 63 import java.util.Collection; 64 import java.util.Date; 65 import java.util.Enumeration; 66 import java.util.HashMap; 67 import java.util.Iterator; 68 import java.util.Map; 69 70 import org.apache.commons.logging.LogFactory; 71 import org.apache.commons.logging.Log; 72 73 import org.apache.commons.betwixt.AttributeDescriptor; 74 import org.apache.commons.betwixt.ElementDescriptor; 75 import org.apache.commons.betwixt.NodeDescriptor; 76 import org.apache.commons.betwixt.XMLIntrospector; 77 78 import org.apache.commons.betwixt.expression.IteratorExpression; 79 import org.apache.commons.betwixt.expression.MethodExpression; 80 import org.apache.commons.betwixt.expression.MethodUpdater; 81 82 import org.apache.commons.betwixt.strategy.PluralStemmer; 83 84 /*** 85 * <p><code>XMLIntrospectorHelper</code> a helper class for 86 * common code shared between the digestor and introspector.</p> 87 * 88 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 89 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 90 * @version $Id: XMLIntrospectorHelper.java,v 1.6 2002/08/14 20:26:22 rdonkin Exp $ 91 */ 92 public class XMLIntrospectorHelper { 93 94 /*** Log used for logging (Doh!) */ 95 protected static Log log = LogFactory.getLog( XMLIntrospectorHelper.class ); 96 97 /*** Base constructor */ 98 public XMLIntrospectorHelper() { 99 } 100 101 /*** 102 * <p> Get the current logging implementation. </p> 103 */ 104 public static Log getLog() { 105 return log; 106 } 107 108 /*** 109 * <p> Set the current logging implementation. </p> 110 */ 111 public static void setLog(Log aLog) { 112 log = aLog; 113 } 114 115 116 /*** 117 * Process a property. 118 * Go through and work out whether it's a loop property, a primitive or a standard. 119 * The class property is ignored. 120 */ 121 public static NodeDescriptor createDescriptor( 122 PropertyDescriptor propertyDescriptor, 123 boolean useAttributesForPrimitives, 124 XMLIntrospector introspector 125 ) throws IntrospectionException { 126 String name = propertyDescriptor.getName(); 127 Class type = propertyDescriptor.getPropertyType(); 128 129 if (log.isTraceEnabled()) { 130 log.trace("Creating descriptor for property: name=" 131 + name + " type=" + type); 132 } 133 134 NodeDescriptor nodeDescriptor = null; 135 Method readMethod = propertyDescriptor.getReadMethod(); 136 Method writeMethod = propertyDescriptor.getWriteMethod(); 137 138 if ( readMethod == null ) { 139 if (log.isTraceEnabled()) { 140 log.trace( "No read method for property: name=" 141 + name + " type=" + type); 142 } 143 return null; 144 } 145 146 if ( log.isTraceEnabled() ) { 147 log.trace( "Read method=" + readMethod.getName() ); 148 } 149 150 // choose response from property type 151 152 // XXX: ignore class property ?? 153 if ( Class.class.equals( type ) && "class".equals( name ) ) { 154 log.trace( "Ignoring class property" ); 155 return null; 156 } 157 if ( isPrimitiveType( type ) ) { 158 if (log.isTraceEnabled()) { 159 log.trace( "Primitive type: " + name); 160 } 161 if ( useAttributesForPrimitives ) { 162 if (log.isTraceEnabled()) { 163 log.trace( "Adding property as attribute: " + name ); 164 } 165 nodeDescriptor = new AttributeDescriptor(); 166 } 167 else { 168 if (log.isTraceEnabled()) { 169 log.trace( "Adding property as element: " + name ); 170 } 171 nodeDescriptor = new ElementDescriptor(true); 172 } 173 nodeDescriptor.setTextExpression( new MethodExpression( readMethod ) ); 174 175 if ( writeMethod != null ) { 176 nodeDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 177 } 178 } 179 else if ( isLoopType( type ) ) { 180 if (log.isTraceEnabled()) { 181 log.trace("Loop type: " + name); 182 } 183 ElementDescriptor loopDescriptor = new ElementDescriptor(); 184 loopDescriptor.setContextExpression( 185 new IteratorExpression( new MethodExpression( readMethod ) ) 186 ); 187 // XXX: need to support some kind of 'add' or handle arrays, Lists or indexed properties 188 //loopDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 189 if ( Map.class.isAssignableFrom( type ) ) { 190 loopDescriptor.setQualifiedName( "entry" ); 191 } 192 193 ElementDescriptor elementDescriptor = new ElementDescriptor(); 194 elementDescriptor.setWrapCollectionsInElement(introspector.isWrapCollectionsInElement()); 195 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } ); 196 197 nodeDescriptor = elementDescriptor; 198 } 199 else { 200 if (log.isTraceEnabled()) { 201 log.trace( "Standard property: " + name); 202 } 203 ElementDescriptor elementDescriptor = new ElementDescriptor(); 204 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) ); 205 if ( writeMethod != null ) { 206 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 207 } 208 209 nodeDescriptor = elementDescriptor; 210 } 211 212 nodeDescriptor.setLocalName( introspector.getNameMapper().mapTypeToElementName( name ) ); 213 if (nodeDescriptor instanceof AttributeDescriptor) { 214 // we want to use the attributemapper only when it is an attribute.. 215 nodeDescriptor.setLocalName( introspector.getAttributeNameMapper().mapTypeToElementName( name ) ); 216 } 217 else { 218 nodeDescriptor.setLocalName( introspector.getElementNameMapper().mapTypeToElementName( name ) ); 219 } 220 221 nodeDescriptor.setPropertyName( propertyDescriptor.getName() ); 222 nodeDescriptor.setPropertyType( type ); 223 224 // XXX: associate more bean information with the descriptor? 225 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() ); 226 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() ); 227 return nodeDescriptor; 228 } 229 230 231 public static void configureProperty( ElementDescriptor elementDescriptor, PropertyDescriptor propertyDescriptor ) { 232 Class type = propertyDescriptor.getPropertyType(); 233 Method readMethod = propertyDescriptor.getReadMethod(); 234 Method writeMethod = propertyDescriptor.getWriteMethod(); 235 236 elementDescriptor.setLocalName( propertyDescriptor.getName() ); 237 elementDescriptor.setPropertyType( type ); 238 239 // XXX: associate more bean information with the descriptor? 240 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() ); 241 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() ); 242 243 if ( readMethod == null ) { 244 log.trace( "No read method" ); 245 return; 246 } 247 248 if ( log.isTraceEnabled() ) { 249 log.trace( "Read method=" + readMethod.getName() ); 250 } 251 252 // choose response from property type 253 254 // XXX: ignore class property ?? 255 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) { 256 log.trace( "Ignoring class property" ); 257 return; 258 } 259 if ( isPrimitiveType( type ) ) { 260 elementDescriptor.setTextExpression( new MethodExpression( readMethod ) ); 261 elementDescriptor.setPrimitiveType(true); 262 } 263 else if ( isLoopType( type ) ) { 264 log.trace("Loop type ??"); 265 266 // don't wrap this in an extra element as its specified in the 267 // XML descriptor so no need. 268 elementDescriptor.setContextExpression( 269 new IteratorExpression( new MethodExpression( readMethod ) ) 270 ); 271 272 writeMethod = null; 273 } 274 else { 275 log.trace( "Standard property" ); 276 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) ); 277 } 278 279 if ( writeMethod != null ) { 280 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 281 } 282 } 283 284 285 public static void configureProperty( AttributeDescriptor attributeDescriptor, PropertyDescriptor propertyDescriptor ) { 286 Class type = propertyDescriptor.getPropertyType(); 287 Method readMethod = propertyDescriptor.getReadMethod(); 288 Method writeMethod = propertyDescriptor.getWriteMethod(); 289 290 if ( readMethod == null ) { 291 log.trace( "No read method" ); 292 return; 293 } 294 295 if ( log.isTraceEnabled() ) { 296 log.trace( "Read method=" + readMethod ); 297 } 298 299 // choose response from property type 300 301 // XXX: ignore class property ?? 302 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) { 303 log.trace( "Ignoring class property" ); 304 return; 305 } 306 if ( isLoopType( type ) ) { 307 log.warn( "Using loop type for an attribute. Type = " + type.getName() + " attribute: " + attributeDescriptor.getQualifiedName() ); 308 } 309 310 log.trace( "Standard property" ); 311 attributeDescriptor.setTextExpression( new MethodExpression( readMethod ) ); 312 313 if ( writeMethod != null ) { 314 attributeDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 315 } 316 317 attributeDescriptor.setLocalName( propertyDescriptor.getName() ); 318 attributeDescriptor.setPropertyType( type ); 319 320 // XXX: associate more bean information with the descriptor? 321 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() ); 322 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() ); 323 } 324 325 326 /*** 327 * Add any addPropety(PropertyType) methods as Updaters 328 * which are often used for 1-N relationships in beans. 329 * <br> 330 * The tricky part here is finding which ElementDescriptor corresponds 331 * to the method. e.g. a property 'items' might have an Element descriptor 332 * which the method addItem() should match to. 333 * <br> 334 * So the algorithm we'll use 335 * by default is to take the decapitalized name of the property being added 336 * and find the first ElementDescriptor that matches the property starting with 337 * the string. This should work for most use cases. 338 * e.g. addChild() would match the children property. 339 */ 340 public static void defaultAddMethods( XMLIntrospector introspector, ElementDescriptor rootDescriptor, Class beanClass ) { 341 // lets iterate over all methods looking for one of the form 342 // add*(PropertyType) 343 if ( beanClass != null ) { 344 Method[] methods = beanClass.getMethods(); 345 for ( int i = 0, size = methods.length; i < size; i++ ) { 346 Method method = methods[i]; 347 String name = method.getName(); 348 if ( name.startsWith( "add" ) ) { 349 // XXX: should we filter out non-void returning methods? 350 // some beans will return something as a helper 351 Class[] types = method.getParameterTypes(); 352 if ( types != null && types.length == 1 ) { 353 String propertyName = Introspector.decapitalize( name.substring(3) ); 354 355 // now lets try find the ElementDescriptor which displays 356 // a property which starts with propertyName 357 // and if so, we'll set a new Updater on it if there 358 // is not one already 359 ElementDescriptor descriptor = findGetCollectionDescriptor( introspector, rootDescriptor, propertyName ); 360 361 if ( log.isDebugEnabled() ) { 362 log.debug( "!! " + propertyName + " -> " + descriptor); 363 } 364 365 if ( descriptor != null ) { 366 descriptor.setUpdater( new MethodUpdater( method ) ); 367 descriptor.setSingularPropertyType( types[0] ); 368 369 if ( log.isDebugEnabled() ) { 370 log.debug( "!! " + method); 371 log.debug( "!! " + types[0]); 372 } 373 374 // is there a child element with no localName 375 ElementDescriptor[] children = descriptor.getElementDescriptors(); 376 if ( children != null && children.length > 0 ) { 377 ElementDescriptor child = children[0]; 378 String localName = child.getLocalName(); 379 if ( localName == null || localName.length() == 0 ) { 380 child.setLocalName( introspector.getElementNameMapper().mapTypeToElementName( propertyName ) ); 381 } 382 } 383 } 384 else { 385 if ( log.isDebugEnabled() ) { 386 log.debug( 387 "Could not find an ElementDescriptor with property name: " 388 + propertyName + " to attach the add method: " + method 389 ); 390 } 391 } 392 } 393 } 394 } 395 } 396 } 397 398 /*** Returns true if the type is a loop type */ 399 public static boolean isLoopType(Class type) { 400 return type.isArray() 401 || Map.class.isAssignableFrom( type ) 402 || Collection.class.isAssignableFrom( type ) 403 || Enumeration.class.isAssignableFrom( type ) 404 || Iterator.class.isAssignableFrom( type ); 405 } 406 407 408 /*** Returns true for primitive types */ 409 public static boolean isPrimitiveType(Class type) { 410 if ( type == null ) { 411 return false; 412 } 413 else if ( type.isPrimitive() ) { 414 return true; 415 } 416 else if ( type.equals( Object.class ) ) { 417 return false; 418 } 419 return type.getName().startsWith( "java.lang." ) 420 || type.isAssignableFrom( Number.class ) 421 || type.isAssignableFrom( String.class ) 422 || type.isAssignableFrom( Date.class ); 423 } 424 425 // Implementation methods 426 //------------------------------------------------------------------------- 427 428 /*** 429 * Attempts to find the element descriptor for the getter property that 430 * typically matches a collection or array. The property name is used 431 * to match. e.g. if an addChild() method is detected the 432 * descriptor for the 'children' getter property should be returned. 433 */ 434 protected static ElementDescriptor findGetCollectionDescriptor( XMLIntrospector introspector, ElementDescriptor rootDescriptor, String propertyName ) { 435 // create the Map of propertyName -> descriptor that the PluralStemmer will choose 436 Map map = new HashMap(); 437 //String propertyName = rootDescriptor.getPropertyName(); 438 if (propertyName != null) { 439 map.put(propertyName, rootDescriptor); 440 } 441 makeElementDescriptorMap( rootDescriptor, map ); 442 443 PluralStemmer stemmer = introspector.getPluralStemmer(); 444 ElementDescriptor elementDescriptor = stemmer.findPluralDescriptor( propertyName, map ); 445 446 if ( log.isTraceEnabled() ) { 447 log.trace( "findPluralDescriptor( " + propertyName + " ):ElementDescriptor=" + elementDescriptor ); 448 } 449 450 return elementDescriptor; 451 } 452 453 /*** 454 * Creates a map where the keys are the property names and the values are the ElementDescriptors 455 */ 456 protected static void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) { 457 ElementDescriptor[] children = rootDescriptor.getElementDescriptors(); 458 if ( children != null ) { 459 for ( int i = 0, size = children.length; i < size; i++ ) { 460 ElementDescriptor child = children[i]; 461 String propertyName = child.getPropertyName(); 462 if ( propertyName != null ) { 463 map.put( propertyName, child ); 464 } 465 makeElementDescriptorMap( child, map ); 466 } 467 } 468 } 469 470 /*** 471 * Traverse the tree of element descriptors and find the oldValue and swap it with the newValue. 472 * This would be much easier to do if ElementDescriptor supported a parent relationship. 473 */ 474 protected static void swapDescriptor( ElementDescriptor rootDescriptor, ElementDescriptor oldValue, ElementDescriptor newValue ) { 475 ElementDescriptor[] children = rootDescriptor.getElementDescriptors(); 476 if ( children != null ) { 477 for ( int i = 0, size = children.length; i < size; i++ ) { 478 ElementDescriptor child = children[i]; 479 if ( child == oldValue ) { 480 children[i] = newValue; 481 break; 482 } 483 swapDescriptor( child, oldValue, newValue ); 484 } 485 } 486 } 487 }

This page was automatically generated by Maven